package net.pws.common.security.spi.resource;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.pws.common.event.ApplicationEventBus;
import net.pws.common.event.ApplicationEventListener;
import net.pws.common.event.ApplicationEventObject;
import net.pws.common.util.StringUtils;
import net.pws.common.security.spi.event.ResourceChangedEventObject;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.thoughtworks.xstream.XStream;

public class ResourceDefinitionRepository implements ApplicationEventListener {
    
    public final static Log logger = LogFactory.getLog(ResourceDefinitionRepository.class);
    
    private final static Resource[] EMPTY_RESOURCES = new Resource[0];
    
    static class SingletonHolder {
        static ResourceDefinitionRepository instance = new ResourceDefinitionRepository();
    }
    
    public static final ResourceDefinitionRepository getInstance() {
        return SingletonHolder.instance;
    }
    
    private List<Resource> resourceList = new ArrayList<Resource>();
    
    private List<Resource> allResourceList = new ArrayList<Resource>();
    
    private Map<String, Resource> resourceMap = new HashMap<String, Resource>();
    
    protected ResourceDefinitionRepository() {
        ApplicationEventBus.getInstance().addListener(this);
        reload();
    }
    
    protected void reload() {
        ResourceDefinitionXmlParser parser = new ResourceDefinitionXmlParser();
        Resource[] resources = parser.parse(ResourceDefinitionRepository.class.getClassLoader()
                                                                              .getResourceAsStream("application.resource.xml"));
        resourceList = Arrays.asList(resources);
        resourceMap = new HashMap<String, Resource>();
        listToMap(resourceList, resourceMap);
        discoverAllList(resourceList, allResourceList);
    }
    
    public final static void listToMap(List<Resource> resList,
                                       Map<String, Resource> resMap) {
        for (Resource resource : resList) {
            resMap.put(resource.getId(), resource);
            listToMap(resource.getChildren(), resMap);
        }
    }
    
    public final static void allowAll(Resource[] reses) {
        if (null == reses) {
            return;
        }
        
        for (Resource resource : reses) {
            resource.setForbidden(false);
            allowAll(resource.getChildren());
        }
    }
    
    public final static void allowAll(List<Resource> reses) {
        if (null == reses) {
            return;
        }
        
        for (Resource resource : reses) {
            resource.setForbidden(false);
            allowAll(resource.getChildren());
        }
    }
    
    public final static void listToMap(Resource[] reses,
                                       Map<String, Resource> resMap) {
        for (Resource resource : reses) {
            resMap.put(resource.getId(), resource);
            listToMap(resource.getChildren(), resMap);
        }
    }
    
    public final static void discoverAllList(List<Resource> resList,
                                             List<Resource> allResList) {
        for (Resource resource : resList) {
            allResList.add(resource);
            discoverAllList(resource.getChildren(), allResList);
        }
    }
    
    public final static void discoverAllList(Resource[] reses,
                                             List<Resource> allResList) {
        for (Resource resource : reses) {
            allResList.add(resource);
            discoverAllList(resource.getChildren(), allResList);
        }
    }
    
    /**
     * @return
     */
    public Resource[] getAllResources() {
        return allResourceList.toArray(EMPTY_RESOURCES);
    }
    
    /**
     * @return
     */
    public int resourceCount() {
        return resourceMap.size();
    }
    
    /**
     * @return the root resources
     */
    public Resource[] getResources() {
        return resourceList.toArray(EMPTY_RESOURCES);
    }
    
    /**
     * @return the root resources
     */
    public Resource[] getResources(String type) {
        if (StringUtils.isEmpty(type)) {
            return EMPTY_RESOURCES;
        }
        
        List<Resource> result = new ArrayList<Resource>();
        
        for (Resource resource : resourceList) {
            if (type.equals(resource.getType())) {
                result.add(resource);
            }
        }
        
        return result.toArray(EMPTY_RESOURCES);
    }
    
    /**
     * @return the resource by specified id
     */
    public Resource getResource(String resourceId) {
        return resourceMap.get(resourceId);
    }
    
    /**
     * @param resourceId
     * @return the children of specified resource
     */
    public Resource[] getResourceChildren(String resourceId) {
        if (StringUtils.isEmpty(resourceId)) {
            return EMPTY_RESOURCES;
        }
        Resource resource = resourceMap.get(resourceId);
        if (null == resource) {
            return EMPTY_RESOURCES;
        }
        
        return resource.getChildren().toArray(EMPTY_RESOURCES);
    }
    
    /**
     * @param resourceId
     * @param type
     * @return the children of specified resource with specified type
     */
    public Resource[] getResourceChildren(String resourceId, String type) {
        if (StringUtils.isEmpty(resourceId) || StringUtils.isEmpty(type)) {
            return EMPTY_RESOURCES;
        }
        
        Resource resource = resourceMap.get(resourceId);
        if (null == resource) {
            return EMPTY_RESOURCES;
        }
        
        List<Resource> result = new ArrayList<Resource>();
        
        for (Resource child : resource.getChildren()) {
            if (type.equals(child.getType())) {
                result.add(child);
            }
        }
        
        return result.toArray(EMPTY_RESOURCES);
    }
    
    // *********************ResourceChangedListener*******************
    public boolean support(ApplicationEventObject event) {
        return (event instanceof ResourceChangedEventObject);
    }
    
    public void handle(ApplicationEventObject event) {
        handle((ResourceChangedEventObject) event);
    }
    
    public void handle(ResourceChangedEventObject eventObject) {
        reload();
    }
    
    public static void main(String[] args) {
        Resource[] resources = ResourceDefinitionRepository.getInstance()
                                                           .getResources();
        XStream xstream = new XStream();
        Resource[] clonedResources = (Resource[]) xstream.fromXML(xstream.toXML(resources));
        System.out.println(resources);
        System.out.println(clonedResources);
    }
    
}
